﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using HeadLinx.AD.Common;
using HeadLinx.AD.Explorer.Properties;
using System.IO;
using System.Diagnostics;
using Microsoft.Win32;

namespace HeadLinx.AD.Explorer
{
    /// <summary>
    /// Interaction logic for ResultsTreeView.xaml
    /// </summary>
    public partial class ChartView : Window
    {
        private HeadLinxUser _rootUser;

        private static double NODE_PADDING_HORIZONTAL = 50.0;
        private static double NODE_PADDING_VERTICAL = 50.0;

        public ChartView(HeadLinxUser rootUser, string title)
        {
            InitializeComponent();

            _rootUser = rootUser;
            
            if (!String.IsNullOrEmpty(title))
            {
                txtTitle.Text = title;
            }
            else
            {
                txtTitle.Visibility = Visibility.Collapsed;
            }

            RestoreLastWindowPosition();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            DrawPersonTree(_rootUser, NODE_PADDING_HORIZONTAL, NODE_PADDING_VERTICAL);
            panZoomViewer.Reset();
        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            SaveLastWindowPosition();
        }

        private void Window_SizeChanged(object sender, RoutedEventArgs e)
        {
            if (this.WindowState == WindowState.Maximized)
            {
                panZoomViewer.Reset();
            }
        }

        //private void Window_KeyUp(object sender, KeyEventArgs e)
        //{
        //    if (e.Key == Key.Escape)
        //    {
        //        panZoomViewer.Reset();
        //    }
        //    else if (e.Key == Key.Left ||
        //        e.Key == Key.Right ||
        //        e.Key == Key.Up ||
        //        e.Key == Key.Down)
        //    {
        //        if (Keyboard.Modifiers == ModifierKeys.Control)
        //        {
        //            // Zoom operation.

        //            if (e.Key == Key.Up)
        //            {
        //                panZoomViewer.ZoomIn();
        //            }
        //            else if (e.Key == Key.Down)
        //            {
        //                panZoomViewer.ZoomOut();
        //            }
        //        }
        //        else
        //        {
        //            // Pan operation.

        //            double panDistance;

        //            if (Keyboard.Modifiers == ModifierKeys.Shift)
        //            {
        //                // Do micro-pan.
        //                panDistance = 20.0;
        //            }
        //            else
        //            {
        //                // Normal pan.
        //                panDistance = 100.0;
        //            }

        //            if (e.Key == Key.Left)
        //            {
        //                panZoomViewer.PanLeft(panDistance);
        //            }
        //            else if (e.Key == Key.Right)
        //            {
        //                panZoomViewer.PanRight(panDistance);
        //            }
        //            else if (e.Key == Key.Up)
        //            {
        //                panZoomViewer.PanUp(panDistance);
        //            }
        //            else if (e.Key == Key.Down)
        //            {
        //                panZoomViewer.PanDown(panDistance);
        //            }
        //        }
        //    }
        //}

        private void Window_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Escape)
            {
                panZoomViewer.Reset();
            }
            else if (e.Key == Key.PageUp || e.Key==Key.Add)
            {
                panZoomViewer.ZoomIn();
            }
            else if (e.Key == Key.PageDown || e.Key==Key.Subtract)
            {
                panZoomViewer.ZoomOut();
            }
            else if (e.Key == Key.Left ||
                e.Key == Key.Right ||
                e.Key == Key.Up ||
                e.Key == Key.Down)
            {
                if (Keyboard.Modifiers == ModifierKeys.Control)
                {
                    // Zoom operation.

                    if (e.Key == Key.Up)
                    {
                        panZoomViewer.ZoomIn();
                    }
                    else if (e.Key == Key.Down)
                    {
                        panZoomViewer.ZoomOut();
                    }
                }
                else
                {
                    // Pan operation.

                    double panDistance;

                    if (Keyboard.Modifiers == ModifierKeys.Shift)
                    {
                        // Do micro-pan.
                        panDistance = 20.0;
                    }
                    else
                    {
                        // Normal pan.
                        panDistance = 100.0;
                    }

                    if (e.Key == Key.Left)
                    {
                        panZoomViewer.PanLeft(panDistance);
                    }
                    else if (e.Key == Key.Right)
                    {
                        panZoomViewer.PanRight(panDistance);
                    }
                    else if (e.Key == Key.Up)
                    {
                        panZoomViewer.PanUp(panDistance);
                    }
                    else if (e.Key == Key.Down)
                    {
                        panZoomViewer.PanDown(panDistance);
                    }
                }
            }
        }

        private void RestoreLastWindowPosition()
        {
            try
            {
                this.WindowState = (WindowState)Enum.Parse(typeof(WindowState), Settings.Default.ChartViewWindowState);

                if (this.WindowState == WindowState.Minimized)
                {
                    // Usability: never start app in minimized state
                    this.WindowState = WindowState.Normal;
                }

                Rect restoreBounds = Rect.Parse(Settings.Default.RestoreBounds);
                this.Left = restoreBounds.Left;
                this.Top = restoreBounds.Top;
                this.Width = restoreBounds.Width;
                this.Height = restoreBounds.Height;
            }
            catch (Exception)
            {
                this.WindowStartupLocation = WindowStartupLocation.CenterScreen;
                this.WindowState = WindowState.Maximized;
            }
        }

        private void SaveLastWindowPosition()
        {
            Settings.Default.ChartViewWindowState = Enum.GetName(typeof(WindowState), this.WindowState);
            Settings.Default.RestoreBounds = this.RestoreBounds.ToString();
            Settings.Default.Save();
        }

        private void DrawPersonTree(HeadLinxUser person, double horizontalPadding, double verticalPadding)
        {
            canvas.Children.Clear();

            int treeDepth = 0; // TODO: fix this to get correct tree depth (it is not working correctly)
            HeadLinxTreeNode rootNode = ConstructUINodesFromPerson(person, ref treeDepth);
            Trace.TraceInformation("Tree depth: " + treeDepth.ToString());

            // First draw all the nodes on the canvas without layout.  We need to do this to get
            // the correct actual sizes of the nodes.
            DrawNodes(canvas, rootNode);

            canvas.UpdateLayout();

            MeasureTree(null, rootNode, horizontalPadding, verticalPadding);

            canvas.Width = (horizontalPadding * 2) + rootNode.TreeWidth;
            canvas.Height = (verticalPadding * 2) + rootNode.TreeHeight;

            // Fill the canvas with a rectangle so user can see the "page".
            Rectangle rectBorder = new Rectangle();
            Canvas.SetLeft(rectBorder, 0);
            Canvas.SetTop(rectBorder, 0);
            Canvas.SetZIndex(rectBorder, -100);
            rectBorder.Width = canvas.Width;
            rectBorder.Height = canvas.Height;
            rectBorder.RadiusX = 12.0;
            rectBorder.RadiusY = 12.0;
            rectBorder.Fill = Brushes.LightSlateGray;
            
            canvas.Children.Add(rectBorder);

            // Draw tree, note we specify the node's left and top properties, which for the first
            // node is simply the padding sizes.
            PositionNodesAndDrawLines(canvas, rootNode, horizontalPadding, 0, horizontalPadding, verticalPadding);

            canvas.UpdateLayout();
        }

        private HeadLinxTreeNode ConstructUINodesFromPerson(HeadLinxUser person, ref int treeDepth)
        {
            HeadLinxTreeNode node = new HeadLinxTreeNode();
            node.Content = new PersonCardView(person);

            bool hasChildren = false;
            foreach (HeadLinxUser childPerson in person.DirectReports.Values)
            {
                hasChildren = true;
                node.Children.Add(ConstructUINodesFromPerson(childPerson, ref treeDepth));
            }

            if (hasChildren)
            {
                treeDepth++;
            }

            return node;
        }

        private static void DrawNodes(Canvas outputCanvas, HeadLinxTreeNode node)
        {
            outputCanvas.Children.Add(node.Content);

            foreach (HeadLinxTreeNode childnode in node.Children)
            {
                DrawNodes(outputCanvas, childnode);
            }
        }

        private static void MeasureTree(HeadLinxTreeNode parentNode, HeadLinxTreeNode node, double horizontalPadding, double verticalPadding)
        {
            if (node.Children.Count == 0)
            {
                // Recursion termination case, node with no children.
                // Tree measurement is simply the measurement of the node.
                node.TreeHeight = node.Height;
                node.TreeWidth = node.Width;
            }
            else
            {
                double maxChildHeight = 0.0;
                double totalWidth = 0.0;

                foreach (HeadLinxTreeNode childNode in node.Children)
                {
                    // Recursively measure child subtree.
                    MeasureTree(node, childNode, horizontalPadding, verticalPadding);

                    // Keep track of longest height of child nodes.
                    if (maxChildHeight < childNode.TreeHeight)
                    {
                        maxChildHeight = childNode.TreeHeight;
                    }

                    // Accumulate length of child nodes.
                    if (totalWidth != 0.0)
                    {
                        // Not first child, add padding.
                        totalWidth += horizontalPadding;
                    }

                    totalWidth += childNode.TreeWidth;
                }

                // Set the max sibling height on each child.
                foreach (HeadLinxTreeNode childNode in node.Children)
                {
                    childNode.MaxSiblingHeight = maxChildHeight;
                }

                node.TreeHeight = node.Height + verticalPadding + maxChildHeight;
                node.TreeWidth = totalWidth;
            }
        }

        private static void PositionNodesAndDrawLines(Canvas canvas, HeadLinxTreeNode node, double absoluteLeft, double absoluteTop, double horizontalPadding, double verticalPadding)
        {
            node.Left = absoluteLeft + (node.TreeWidth / 2.0) - (node.Width / 2.0);
            node.Top = absoluteTop + verticalPadding;
            //canvas.Children.Add(node.Content);

            double childAbsoluteTop = node.Top + node.Height;
            // TODO: following attempt to align to largest sibling is broken
            //double childAbsoluteTop = node.Top + Math.Max(node.Height, node.MaxSiblingHeight);
            double childAbsoluteLeft = absoluteLeft;

            foreach (HeadLinxTreeNode childNode in node.Children)
            {
                // Recursively position child.
                PositionNodesAndDrawLines(canvas, childNode, childAbsoluteLeft, childAbsoluteTop, horizontalPadding, verticalPadding);

                // Draw line from node to child node.
                DrawTreeNodeLine(canvas, node, childNode);

                childAbsoluteLeft += childNode.TreeWidth + horizontalPadding;
            }
        }

        private static void DrawTreeNodeLine(Canvas canvas, HeadLinxTreeNode node, HeadLinxTreeNode childNode)
        {
            // Draw line from node to child node.
            Line line = new Line();

            line.Stroke = Brushes.WhiteSmoke;
            line.StrokeEndLineCap = PenLineCap.Round;
            line.StrokeThickness = 2.0;

            // Start at middle bottom of parent node.
            line.X1 = node.Left + (node.Width / 2.0);
            line.Y1 = node.Top + node.Height;

            // End at middle top of child node.
            line.X2 = childNode.Left + (childNode.Width / 2.0);
            line.Y2 = childNode.Top;

            Canvas.SetZIndex(line, -10);
            canvas.Children.Add(line);
        }

        private void SaveChart()
        {
            try
            {
                if (_rootUser != null)
                {
                    SaveFileDialog dlg = new SaveFileDialog();
                    dlg.Title = "Save HeadLinx Chart";
                    dlg.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
                    dlg.AddExtension = true;
                    dlg.Filter = "HeadLinx Chart (*.hlx)|*.hlx";
                    dlg.DefaultExt = "hlx";
                    dlg.RestoreDirectory = true;
                    dlg.FileName = ConstructDefaultFileName();
                    dlg.CheckFileExists = false;

                    LocalUtility.LoadInitialDialogFolder(dlg);

                    bool? ok = dlg.ShowDialog();

                    if (ok.Value == true)
                    {
                        LocalUtility.SaveInitialDialogFolder(dlg);

                        this.Cursor = Cursors.Wait;

                        using (FileStream fs = new FileStream(dlg.FileName, FileMode.Create, FileAccess.Write))
                        {
                            _rootUser.SaveToXml(fs);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
            finally
            {
                this.Cursor = null;
            }
        }

        private string ConstructDefaultFileName()
        {
            DateTime now = DateTime.Now;
            return String.Format("HeadLinx Chart {0:0000}-{1:00}-{2:00}.hlx",
                now.Year, now.Month, now.Day);
        }

        private void btnResetView_Click(object sender, RoutedEventArgs e)
        {
            panZoomViewer.Reset();
        }

        private void btnPrintChart_Click(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("Not yet implemented");
            //PrintDialog dlg = new PrintDialog();

            //if (dlg.ShowDialog() == true)
            //{
            //    dlg.PrintVisual(canvas, "HeadLinx Chart");
            //}
        }

        private void menuSaveChart_Click(object sender, RoutedEventArgs e)
        {
            SaveChart();
        }

        private void menuStats_Click(object sender, RoutedEventArgs e)
        {
            int userCount, selectedUserCount;
            HeadLinxUtility.GetUserStats(_rootUser, out userCount, out selectedUserCount);

            MessageBox.Show(String.Format("Total Contacts: {0}{1}Selected Contacts: {2}",
                userCount, Environment.NewLine, selectedUserCount), "HeadLinx Chart Statistics");
        }

        private void menuClipAll_Click(object sender, RoutedEventArgs e)
        {
            LocalUtility.ClipAllInformation(_rootUser, true);
        }

        private void menuClipContact_Click(object sender, RoutedEventArgs e)
        {
            LocalUtility.ClipContactInformation(_rootUser, true);
        }

        private void mnuExit_Click(object sender, RoutedEventArgs e)
        {
            this.Close();
        }

        private void menuExportCanvas_Click(object sender, RoutedEventArgs e)
        {
            SaveFileDialog dlg = LocalUtility.GetSaveFileDialog();
            dlg.Filter = "PNG Image (*.png)|*.png|XPS Document (*.xps)|*.xps";
            dlg.Title = "Export Chart As";

            bool? ok = dlg.ShowDialog();

            if (ok ?? ok.Value)
            {
                string outputFile = dlg.FileName;

                if (dlg.FilterIndex == 1)
                {
                    LocalUtility.SaveCanvasToPNG(canvas, outputFile);
                }
                else if (dlg.FilterIndex == 2)
                {
                    LocalUtility.SaveCanvasToXPS(canvas, outputFile);
                }
            }
        }
    }
}
